<?php
/**
 * Created by laoyu.
 * User: hilaoyu@qq.com
 * Date: 2018/7/15
 * Time: 上午10:17
 */

namespace Hilaoyu\Utils;


class UtilStr
{

    /**
     * Convert the given string to lower-case.
     *
     * @param string $value
     * @return string
     */
    public static function lower($value)
    {
        return mb_strtolower($value, 'UTF-8');
    }

    /**
     * Convert the given string to upper-case.
     *
     * @param string $value
     * @return string
     */
    public static function upper($value)
    {
        return mb_strtoupper($value, 'UTF-8');
    }


    public static function toUtf8($str)
    {
        $encode = mb_detect_encoding($str, array("ASCII", 'UTF-8', "GB2312", "GBK", 'BIG5'));
        if ($encode == 'UTF-8') {
            return $str;
        } else {
            return mb_convert_encoding($str, 'UTF-8', $encode);
        }
    }


    /**
     * Returns the portion of string specified by the start and length parameters.
     *
     * @param string $string
     * @param int $start
     * @param int|null $length
     * @return string
     */
    public static function substr($string, $start, $length = null)
    {
        return mb_substr($string, $start, $length, 'UTF-8');
    }

    /**
     * Make a string's first character uppercase.
     *
     * @param string $string
     * @return string
     */
    public static function ucFirst($string)
    {
        return static::upper(static::substr($string, 0, 1)) . static::substr($string, 1);
    }

    /**
     * Make a string's first character lowercase.
     *
     * @param string $string
     * @return string
     */
    public static function LcFirst($string)
    {
        return lcfirst($string);
    }


    /**
     * Return the remainder of a string after a given value.
     *
     * @param string $subject
     * @param string $search
     * @return string
     */
    public static function after($subject, $search)
    {
        return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
    }

    /**
     * Get the portion of a string before a given value.
     *
     * @param string $subject
     * @param string $search
     * @return string
     */
    public static function before($subject, $search)
    {
        return $search === '' ? $subject : explode($search, $subject)[0];
    }

    /**
     * Determine if a given string starts with a given substring.
     *
     * @param string $haystack
     * @param string|array $needles
     * @return bool
     */
    public static function startsWith($haystack, $needles)
    {
        foreach ((array)$needles as $needle) {
            if ($needle !== '' && substr($haystack, 0, strlen($needle)) === (string)$needle) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determine if a given string contains a given substring.
     *
     * @param string $haystack
     * @param string|array $needles
     * @param bool $strict 大小写严格
     * @return bool
     */
    public static function contains($haystack, $needles, $strict = true)
    {
        foreach ((array)$needles as $needle) {
            if ('' === $needle) {
                continue;
            }
            $has = false;
            if ($strict) {
                $has = false !== mb_strpos($haystack, $needle);
            } else {
                $has = false !== mb_stripos($haystack, $needle);
            }
            if ($has) {
                return true;
            }

        }

        return false;
    }

    /**
     * Determine if a given string ends with a given substring.
     *
     * @param string $haystack
     * @param string|array $needles
     * @return bool
     */
    public static function endsWith($haystack, $needles)
    {
        foreach ((array)$needles as $needle) {
            if (substr($haystack, -strlen($needle)) === (string)$needle) {
                return true;
            }
        }

        return false;
    }

    /**
     * Begin a string with a single instance of a given value.
     *
     * @param string $value
     * @param string $prefix
     * @return string
     */
    public static function start($value, $prefix)
    {
        $quoted = preg_quote($prefix, '/');

        return $prefix . preg_replace('/^(?:' . $quoted . ')+/u', '', $value);
    }

    /**
     * Cap a string with a single instance of a given value.
     *
     * @param string $value
     * @param string $cap
     * @return string
     */
    public static function finish($value, $cap)
    {
        $quoted = preg_quote($cap, '/');

        return preg_replace('/(?:' . $quoted . ')+$/u', '', $value) . $cap;
    }


    /**
     * Return the length of the given string.
     *
     * @param string $value
     * @param string $encoding
     * @return int
     */
    public static function length($value, $encoding = null)
    {
        if ($encoding) {
            return mb_strlen($value, $encoding);
        }

        return mb_strlen($value);
    }

    /**
     * Limit the number of characters in a string.
     *
     * @param string $value
     * @param int $limit
     * @param string $end
     * @return string
     */
    public static function limit($value, $limit = 100, $end = '...')
    {
        if (mb_strwidth($value, 'UTF-8') <= $limit) {
            return $value;
        }

        return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')) . $end;
    }

    /**
     * 生成随机字符串
     * @param int $length 生成随机字符串的长度
     * @param string $charset 自定义生成字符串的范围
     * @return string  生成的随机字符串
     */
    static public function randString($length = 32, $charset = '')
    {
        $charset = trim($charset);
        if (!$charset) {
            $charset .= '0123456789abcdefghjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        }
        $string = '';
        $chars = str_split($charset);

        for ($i = 0; $i < $length; $i++) {
            $string .= $chars[array_rand($chars)];
        }

        return $string;
    }

    /**
     * 生成随机密码
     * @param int $length
     * @param false $no_special 默认false  false-默认包含特殊字符； true-不包含特殊字符
     * @return string
     */
    static public function randPassword($length = 8, $no_special = false, $special = [])
    {
        $digits = array_flip(range('0', '9'));
        $lowercase = array_flip(range('a', 'z'));
        $uppercase = array_flip(range('A', 'Z'));
        $special = (array)$special;
        if (!empty($special)) {
            $special = array_flip($special);
        } else {
            $special = array_flip(str_split('!@#$%&*?'));
        }

        if ($no_special) {
            $special = [];
        }
        $combined = array_merge($digits, $lowercase, $uppercase, $special);

        $rand_len = $length - 4;
        if ($no_special) {
            $rand_len += 1;
        }
        $password = array_rand($lowercase) . str_shuffle(
                array_rand($digits) .
                array_rand($uppercase) .
                (!$no_special ? array_rand($special) : '') .
                implode(array_rand($combined, $rand_len)));


        return $password;
    }

    static public function underlineToHump($str)
    {
        $str = preg_replace_callback('/([-_]+([a-z]{1}))/i', function ($matches) {
            return static::upper($matches[2]);
        }, $str);
        return $str;
    }

    static public function humpToUnderline($str)
    {
        $str = preg_replace_callback('/([A-Z]{1})/', function ($matches) {
            return '_' . static::lower($matches[0]);
        }, $str);
        return $str;
    }

    /**
     * 字符串逻辑运算 支持 |或 &与 !非 和括号()
     * @param $str 要运算字符串  如 1&((1|0)&!0)|1
     * @param null $callback 闭包方法，会代入解析出的每个字符串，如果为空则直接转为bool
     * @return bool|mixed
     */
    static public function stringLogicalCalculate($str, $callback = null)
    {
        if (preg_match("/^([^\(]*?)([\|&])(.*)$/is", $str, $matches)) {
            //dump('1',$matches,'1');
            $re = static::stringLogicalCalculate($matches[1], $callback);
            if (($matches[2] == '|')) {
                $re = $re || static::stringLogicalCalculate($matches[3], $callback);
            } else {
                $re = $re && static::stringLogicalCalculate($matches[3], $callback);
            }

            return $re;
        } elseif (preg_match("/^\((.*)\)([\|&]?)(.*)$/is", $str, $matches)) {
            //dump('2',$matches,'2');
            $re = static::stringLogicalCalculate($matches[1], $callback);
            if ($matches[2] != '') {
                if (($matches[2] == '|')) {
                    $re = $re || static::stringLogicalCalculate($matches[3], $callback);
                } else {
                    $re = $re && static::stringLogicalCalculate($matches[3], $callback);
                }
            }

            return $re;

        } else {
            //dump('-',$str,'-');
            $tmp = true;
            if (substr($str, 0, 1) == '!') {
                $tmp = false;
            }

            $str = ltrim($str, '!');

            if ($callback instanceof \Closure) {
                $re = $callback($str);
            } else {
                $re = (bool)$str;
            }


            return $tmp === (bool)$re;

        }

    }


    static public function clearRepeatedChar($sting, $chars = ' ', $replace = null)
    {
        $chars = (array)$chars;
        if (!is_null($replace) && in_array($replace, $chars)) {
            $chars[] = $replace;
        }
        foreach ($chars as $char) {

            if (is_null($replace)) {
                $replaceChar = $char;
            } else {
                $replaceChar = $replace;
            }
            $char = preg_quote($char, '/');
            $pattern = "/[{$char}]+/is";

            //dump($pattern,$char);
            $sting = preg_replace($pattern, $replaceChar, $sting);
        }


        return $sting;
    }


    static public function replace($content, array $replaces,$quote_key=true)
    {

        if (is_array($replaces) && !empty($replaces)) {
            $preg_replaces = [];
            foreach ($replaces as $key => $value) {
                if($quote_key){
                    $key = preg_quote($key, '/');
                }
                $preg_replaces['/' . $key . '/u'] = function ($match) use ($value) {
                    if ($value instanceof \Closure) {
                        $value = call_user_func($value, $match[0]);
                    } else {
                        $value = (string)$value;
                    }
                    return $value;
                };

            }

            $content = preg_replace_callback_array($preg_replaces, $content);
        }

        return $content;
    }

    /**
     * Replace the first occurrence of a given value in the string.
     *
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @return string
     */
    public static function replaceFirst($search, $replace, $subject)
    {
        if ($search == '') {
            return $subject;
        }

        $position = strpos($subject, $search);

        if ($position !== false) {
            return substr_replace($subject, $replace, $position, strlen($search));
        }

        return $subject;
    }

    /**
     * Replace the last occurrence of a given value in the string.
     *
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @return string
     */
    public static function replaceLast($search, $replace, $subject)
    {
        $position = strrpos($subject, $search);

        if ($position !== false) {
            return substr_replace($subject, $replace, $position, strlen($search));
        }

        return $subject;
    }

    /**
     * @param string $prefix
     * @param int $rand $rand
     * @param int $to_base 10 <= $to_base <= 36
     * @param bool $strto TRUE-strtoupper
     * @param int $offset
     * @return string
     */
    public static function timeCode($prefix = '', $rand = 2, $to_base = 36, $strto = TRUE, $offset = 1000000000)
    {
        $time = time();
        if ($rand < 1 || $rand > 10) return '';
        $rand_min = 10 ** $rand;
        $rand_max = 10 ** ($rand + 1) - 1;
        if (self::length($offset) == 4){
            $offset = strtotime("{$offset}-01-01 00:00:00");
        }
        $time = $time - $offset;
        $input = $time * $rand_min + rand($rand_min, $rand_max);

        if ($to_base < 10 || $to_base > 36) return '';

        $str = base_convert($input, 10, $to_base);

        if (TRUE == $strto) {
            $str = strtoupper($str);
        }

        $str = $prefix . $str;

        return $str;
    }

    /** 字符串数据脱敏
     * 比如手机号17623556644，默认模式脱敏结果为176****6644；
     * 可以传参数，maskString(17623556644, 1,2)，脱敏结果为1********44；
     * @param string $string
     * @param int $preserveFront
     * @param int $preserveBack
     * @param string $maskSymbol
     * @return string
     */
    public static function maskString(string $string, int $preserveFront = 3, int $preserveBack = 4, string $maskSymbol = '*'): string
    {
        $stringLength = strlen($string);

        if ($stringLength <= $preserveFront + $preserveBack) {
            return $string;
        }

        $maskLength = $stringLength - $preserveFront - $preserveBack;

        $frontPart = substr($string, 0, $preserveFront);
        $backPart = substr($string, -$preserveBack);

        return $frontPart . str_repeat($maskSymbol, $maskLength) . $backPart;
    }

}
